﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;

namespace PyNet
{
    class clsClientThread
    {
        private TcpListener listener;
        private Thread thread;
        private Dictionary<int, clsClient> clients = new Dictionary<int, clsClient>();
        private List<int> threads = new List<int>();
        private int state = 0;
        private int port;

        public Dictionary<int, clsChannel> Channels = new Dictionary<int, clsChannel>();
        public int LobbyID;
        public int VoidID;
        public int AdminLobbyID;

        public Dictionary<int, clsClient> GetClients()
        {
            return clients;
        }

        public int CreateChannel(string name, int flags, int owner)
        {
            int ChanID = 0;
            foreach (int i in Channels.Keys)
            {
                if (i > ChanID)
                    ChanID = i;
                if (Channels[i].Name.ToLower() == name.ToLower())
                {
                    return -1;
                }
            }
            ChanID++;
            Channels.Add(ChanID, new clsChannel(name, flags, owner, ChanID));
            Program.GetLogger.Write("DarkOrange", "Created channel #" + ChanID.ToString() + ": " + name);
            return ChanID;
        }

        public int FindChannel(string name)
        {
            foreach (int i in Channels.Keys)
            {
                if (Channels[i].Name.ToLower() == name.ToLower())
                    return i;
            }
            return -1;
        }

        public int FindUser(string name)
        {
            foreach (int i in clients.Keys)
            {
                if (clients[i].Username.ToLower() == name.ToLower())
                    return i;
            }
            return -1;
        }

        public clsClient FindUserObject(int id)
        {
            if (clients.Keys.Contains(id))
            {
                return clients[id];
            }
            return null;
        }

        public bool IsOnline()
        {
            return state == 1;
        }

        public clsClientThread()
        {
            if (IsOnline())
                StartServerLoop();

        }

        public void SendGlobalPingRequest()
        {
            Program.GetLogger.Write("DarkSlateGray", "Pinging " + clients.Count.ToString() + " client(s)...");
            foreach (int i in clients.Keys)
            {
                clients[i].SendPingRequest(i);
            }
        }

        public void SendAdminChatEvent(int id, string username, string data, int flags, int mod)
        {
            foreach (int i in clients.Keys)
            {
                if (clients[i].IsAdmin())
                    clients[i].SendChatEvent(id, username, flags, mod, data);
            }
        }

        public void SendGlobalChatEvent(int id, string username, string data, int flags, int mod)
        {
            foreach (int i in clients.Keys)
            {
                clients[i].SendChatEvent(id, username, flags, mod, data);
            }
        }

        public void SendGlobalBroadcast(string text)
        {
            SendGlobalChatEvent(ChatEvents.ServerBroadcast, "Server", text, 0, 0);
        }

        public bool StartServerLoop()
        {
            if (thread != null && thread.ThreadState == ThreadState.Running)
                StopServerLoop();

            port = Properties.Settings.Default.serverPort;
            int maxPendingConnections = 5;
            Program.GetConsole.RemoveListItem("");
            Program.GetLogger.Write("DarkSlateGray", "Starting server on port " + port.ToString() + "...");

            Channels.Clear();
            clients.Clear();
            threads.Clear();

            listener = new TcpListener(System.Net.IPAddress.Any, port);
            try
            {
                listener.Start(maxPendingConnections);
            }
            catch (SocketException e)
            {
                if (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
                {
                    Program.GetLogger.Write("DarkRed", "Server could not start: address in use");
                    return false;
                }
                else
                {
                    Program.GetLogger.Write("DarkRed", "Server could not start: " + e.ToString());
                    return false;
                }
            }

            // Init channels
            LobbyID = CreateChannel("lobby", ChannelFlags.IsLobby, 0);
            VoidID = CreateChannel("The Void", ChannelFlags.IsVoid, 0);
            AdminLobbyID = CreateChannel("Backstage", ChannelFlags.IsLobby + ChannelFlags.IsPrivate, 0);

            thread = new Thread((ThreadStart)ServerLoop);
            thread.IsBackground = true;
            thread.Start();

            Program.GetConsole.SetTitle("Online");
            Program.GetLogger.Write("DarkSlateGray", "Server started and running...");
            Program.GetConsole.SetLabel("Server online, port " + port.ToString() + ", no clients connected yet");

            state = 1;

            return true;
        }

        public bool StopServerLoop()
        {
            thread.Abort();
            listener.Stop();

            Program.GetLogger.Write("DarkSlateGray", "Server stopped, all clients disconnected");
            Program.GetConsole.SetLabel("Server offline");
            Program.GetConsole.RemoveListItem("");
            for (int i = 0; i < clients.Count; )
            {
                if (clients.ContainsKey(i))
                {
                    clients[i].Kill();
                }
                i++;
            }
            Program.GetConsole.SetTitle("");

            state = 0;
            return true;
        }

        public void FindAndRemoveClient(string username)
        {
            foreach (int a in threads)
            {
                if (clients.Keys.Contains(a))
                {
                    if (clients[a].Username.ToLower().Contains(username.ToLower()))
                    {
                        clients[a].Kill("Connection terminated forcefully by the server");
                        clients.Remove(a);
                        Program.GetConsole.AddText("DarkRed", "Server removed user: " + clients[a].Username);
                    }
                }
            }
        }

        private void ServerLoop()
        {
            while (true)
            {
                if (listener.Pending())
                {
                    if (clients.Count < Properties.Settings.Default.serverCapacity)
                    {
                        Socket clientSocket = listener.AcceptSocket();
                        int id = 1;
                        while (threads.Contains(id))
                            id++;
                        clsClient client = new clsClient(clientSocket, id);
                        threads.Add(id);
                        clients.Add(id, client);

                        Program.GetConsole.SetBar(clients.Count, Properties.Settings.Default.serverCapacity);
                        Program.GetConsole.SetLabel("Server online, port " + port.ToString() + ", " + clients.Count + " clients connected");

                        client.Write("Client #" + id.ToString() + " (" + clientSocket.RemoteEndPoint.ToString() + ") connected!");
                    }
                    else
                    {
                        Socket client = listener.AcceptSocket();
                        client.Close();
                        Program.GetLogger.Write("DarkSlateGray", "Server reached maxed capacity! (" + clients.Count.ToString() + "/" + Properties.Settings.Default.serverCapacity.ToString() + ")");
                    }
                }

                // Remove old clients
                List<int> pending = new List<int>();
                bool changed = false;
                foreach(int a in clients.Keys)
                {
                    if ((!clients[a].IsOnline()) || clients[a].LastKA() > 450)
                    {
                        pending.Add(a);
                    }
                    else
                    {
                        TimeSpan diff = (DateTime.Now - clients[a].protocolLastPing);
                        if (diff.Seconds >= 30)
                            clients[a].SendPingRequest(clients[a].ClientID);
                    }
                }

                foreach (int b in pending)
                {
                    clients[b].Kill();
                    clients.Remove(b);
                    changed = true;
                }

                if (changed)
                    Program.GetConsole.SetLabel("Server online, port " + port.ToString() + ", " + clients.Count + " clients connected");

                Thread.Sleep(25);
            }
        }
    }
}
